package net.gdface.utils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.base.Ticker;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.Weigher;

import net.gdface.guava.ObjectDeepEquals;

import static com.google.common.base.Preconditions.checkNotNull;
import static net.gdface.reflection.ConstructorUtils.invokeConstructor;
import static com.google.common.base.Preconditions.checkArgument;
/**
 * 基于GUAVA {@link LoadingCache}实现类型转转换接口{@link Function},
 * 当一个输入K的计算结果已经存在于缓存时，直接返回，无需再次计算，
 * 避免对同一个输入数据多次重复计算。
 * 适用于输入值与结果恒定对应的场景
 * @author guyadong
 *
 * @param <K> 输入参数类型
 * @param <V> 输出参数类型
 * @deprecated move to {@link net.gdface.cache.FunctionCached}
 */
public class FunctionCached<K,V> implements Function<K, V> {
	/**
	 * 输入值与计算结果的映射，计算结果可能是正常计算出的V值,也可能是异常(Exception)
	 */
	protected final LoadingCache<K, Object> cache;
	protected final Function<K, V> getterFunction;
	protected final V defaultValue;
	/**
	 * 构造方法
	 * @param getterFunction 原类型转换接口实现
	 * @param defaultValue    K为{@code null}时返回的默认值
	 * @param cacheBuilder    外部提供的CacheBuilder实例,为{@code null}则忽略
	 * @since 2.7.9
	 */
	public FunctionCached(final Function<K, V> getterFunction,V defaultValue,CacheBuilder<Object,Object> cacheBuilder){
		this.getterFunction = checkNotNull(getterFunction,"getterFunction is null");
		/**  输入参数不可以是缓存实例  */
		checkArgument(!(getterFunction instanceof FunctionCached),"getterFunction must not be instance of %s",getClass().getName());
		this.defaultValue = defaultValue;
		if(null == cacheBuilder ) {
			cacheBuilder= CacheBuilder.newBuilder();
		}
		this.cache = cacheBuilder.build(new CacheLoader<K, Object>(){
			@Override
			public Object load(K key) {
				try {
					return asCached(getterFunction.apply(key));
				} catch (Exception e) {
					return e;
				}
			}});
	}
	/**
	 * 构造方法
	 * @param getterFunction 原类型转换接口实现
	 * @param defaultValue    K为{@code null}时返回的默认值
	 */
	public FunctionCached(final Function<K, V> getterFunction,V defaultValue){
		this(getterFunction,defaultValue,null);
	}
	/**
	 * 构造方法,
	 * K 为{@code null}时返回{@code null}
	 * @param getterFunction 原类型转换接口实现
	 */
	public FunctionCached(Function<K, V> getterFunction){
		this(getterFunction, null);
	}
	/**
	 * 将 {@link LoadingCache}的值存储类型转为Value
	 * @param cached
	 */
	@SuppressWarnings("unchecked")
	protected V asValue(Object cached ){
		return (V) cached;
	}
	/**
	 * 将 value转为 {@link LoadingCache}的值存储类型
	 * @param value
	 */
	protected Object asCached(V value ){
		return value;
	}	
	
	/**
	 * 非缓存调用
	 * @param key
	 * @return {@code key}为{@code null}返回{@link #defaultValue}
	 */
	public V getUncached(K key){
		return null == key ? defaultValue: asValue(getterFunction.apply(key));
	}
	/**
	 * 缓存调用
	 * @param key
	 * @return {@code key}为{@code null}返回{@link #defaultValue}
	 */
	public V get(K key){
		if(null != key){
			Object value = cache.getUnchecked(key);
			if(value instanceof Exception) {
				Throwables.throwIfUnchecked((Exception)value);
				throw new RuntimeException((Exception)value);
			}
			return asValue(value);
		}
		return defaultValue;
	}
	
	/**
	 * 缓存调用
	 * @see #get(Object)
	 * @see com.google.common.base.Function#apply(java.lang.Object)
	 */
	@Override
	public V apply(K input) {
		return get(input);
	}
	/**
	 * 返回{@code getterFunction}的{@link FunctionCached}实例,
	 * 如果{@code getterFunction}为{@link FunctionCached}实例,则直接返回，否则创建新实例
	 * @param getterFunction
	 * @param defaultValue
	 * @param cacheBuilder 外部提供的CacheBuilder实例,为{@code null}则忽略
	 * @see #FunctionCached(Function, Object, CacheBuilder)
	 * @since 2.7.9
	 */
	public static <K, V> FunctionCached<K, V> of(Function<K, V> getterFunction,V defaultValue,CacheBuilder<Object,Object> cacheBuilder){
		if(getterFunction instanceof FunctionCached){
			return (FunctionCached<K, V>) getterFunction;
		}
		return builder().getterFunction(getterFunction, defaultValue).build();
	}
	/**
	 * 返回{@code getterFunction}的{@link FunctionCached}实例,
	 * 如果{@code getterFunction}为{@link FunctionCached}实例,则直接返回，否则创建新实例
	 * @param getterFunction
	 * @param defaultValue
	 * @see #of(Function, Object, CacheBuilder)
	 */
	public static <K, V> FunctionCached<K, V> of(Function<K, V> getterFunction,V defaultValue){
		return of(getterFunction, defaultValue,null); 
	}
	/**
	 * 返回{@code getterFunction}的{@link FunctionCached}实例(默认实例为{@code null})
	 * @param getterFunction
	 * @see #of(Function, Object)
	 */
	public static <K, V> FunctionCached<K, V> of(Function<K, V> getterFunction){
		return of(getterFunction,null); 
	}
	public static<K, V>Builder<K, V> builder(){
		return new Builder<>();
	}
	/**
	 * builder for FunctionCached
	 * @author guyadong
	 *
	 * @param <K>
	 * @param <V>
	 * @since 2.9.6
	 */
	public static class Builder<K, V>{
		private CacheBuilder<Object, Object> cacheBuilder;
		private Function<K, V> getterFunction;
		private V defaultValue;
		private boolean nullable;

		protected Builder() {
			this.cacheBuilder = CacheBuilder.newBuilder();
		}

		protected Builder(CacheBuilder<Object, Object> cacheBuilder, Function<K, V> getterFunction, V defaultValue,
				boolean nullable) {
			this.cacheBuilder = cacheBuilder;
			this.getterFunction = getterFunction;
			this.defaultValue = defaultValue;
			this.nullable = nullable;
		}
		public <V1> Builder<Object[],V1> invokeFunction( MethodInvokeFunction<V1> invokeFunction){
			return new Builder<Object[],V1>().keyDeepEqual().getterFunction(invokeFunction);
		}
		@SuppressWarnings("unchecked")
		public<K1, V1> Builder<K1, V1> getterFunction( Function<K1, V1> getterFunction){
			return new Builder<K1, V1>(this.cacheBuilder,checkNotNull(getterFunction,"getterFunction is null"),(V1)defaultValue,this.nullable);
		}
		public <K1, V1> Builder<K1, V1> getterFunction( Function<K1, V1> getterFunction,V1 defaultValue){
			return new Builder<K1, V1>(this.cacheBuilder,checkNotNull(getterFunction,"getterFunction is null"),defaultValue,this.nullable);
		}

		/**
		 * 指定当{@link #getterFunction}返回值为{@code null}或{@link FunctionCached#get(Object)}参数为{@code null}时返回的默认值,
		 * 默认为{@code null}
		 * @param defaultValue
		 */
		public Builder<K, V> defaultValue(V defaultValue) {
			this.defaultValue = defaultValue;
			return this;
		}
		/**
		 * 对K执行深度比较，<p>
		 * 对于K为数组类型时，必须指定深度比较
		 * @see CacheBuilder#keyEquivalence(Equivalence)  
		 */
		public Builder<K, V> keyDeepEqual() {
			ObjectDeepEquals.keyDeepEqual(cacheBuilder);
			return this;
		}
		/**
		 * 允许 {@link #getterFunction}返回值为{@code null}
		 */
		public Builder<K, V> nullable() {
			nullable = true;
			return this;
		}
		/**
		 * @see CacheBuilder#initialCapacity(int)
		 */
		public Builder<K, V> initialCapacity(int initialCapacity) {
			cacheBuilder.initialCapacity(initialCapacity);
			return this;
		}

		/**
		 * @see CacheBuilder#concurrencyLevel(int)
		 */
		public Builder<K, V> concurrencyLevel(int concurrencyLevel) {
			cacheBuilder.concurrencyLevel(concurrencyLevel);
			return this;
		}

		/**
		 * @see CacheBuilder#maximumSize(long)
		 */
		public Builder<K, V> maximumSize(long size) {
			cacheBuilder.maximumSize(size);
			return this;
		}

		/**
		 * @see CacheBuilder#maximumWeight(long)
		 */
		public Builder<K, V> maximumWeight(long weight) {
			cacheBuilder.maximumWeight(weight);
			return this;
		}

		/**
		 * @see CacheBuilder#weigher(Weigher)
		 */
		public <K1, V1> Builder<K, V> weigher(Weigher<? super K1, ? super V1> weigher) {
			cacheBuilder.weigher(weigher);
			return this;
		}

		/**
		 * @see CacheBuilder#weakValues()
		 */
		public Builder<K, V> weakValues() {
			cacheBuilder.weakValues();
			return this;
		}

		/**
		 * @see CacheBuilder#softValues()
		 */
		public Builder<K, V> softValues() {
			cacheBuilder.softValues();
			return this;
		}

		/**
		 * @see CacheBuilder#expireAfterWrite(long, TimeUnit)
		 */
		public Builder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
			cacheBuilder.expireAfterWrite(duration, unit);
			return this;
		}

		/**
		 * @see CacheBuilder#expireAfterAccess(long, TimeUnit)
		 */
		public Builder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
			cacheBuilder.expireAfterAccess(duration, unit);
			return this;
		}

		/**
		 * @see CacheBuilder#refreshAfterWrite(long, TimeUnit)
		 */
		public Builder<K, V> refreshAfterWrite(long duration, TimeUnit unit) {
			cacheBuilder.refreshAfterWrite(duration, unit);
			return this;
		}

		/**
		 * @see CacheBuilder#ticker(Ticker)
		 */
		public Builder<K, V> ticker(Ticker ticker) {
			cacheBuilder.ticker(ticker);
			return this;
		}

		/**
		 * @see CacheBuilder#removalListener(RemovalListener)
		 */
		public Builder<K, V> removalListener(RemovalListener<? super K, ? super V> listener) {
			cacheBuilder.removalListener(listener);
			return this;
		}
		/**
		 * @see CacheBuilder#recordStats()
		 */
		public Builder<K, V> recordStats() {
			cacheBuilder.recordStats();
			return this;
		}

		public FunctionCached<K,V> build() {
			if(nullable) {
				return new FunctionCached<K,V>(getterFunction, defaultValue, cacheBuilder) {
					final Optional<V> defOpt = Optional.fromNullable(defaultValue);
					@SuppressWarnings("unchecked")
					@Override
					protected V asValue(Object cached) {
						return ((Optional<V>)cached).or(defOpt).orNull();
					}

					@Override
					protected Object asCached(V value) {
						return Optional.fromNullable(value);
					}
				};
			}
			return new FunctionCached<>(getterFunction, defaultValue, cacheBuilder);
		}
		/**
		 * 将当前实例作为参数反射构建指定类型的实例<br>
		 * 要求目标类型构造方法第一个参数类型必须是{@link Builder}<p>
		 * 如果 {@code otherArgs}不为空要求构造方法第二个及以后的所有参数的类型能匹配{@code otherArgs}的类型，
		 * 否则抛出异常<p>
		 * 参见 {@link net.gdface.cache.SingletonCache}
		 * @param <C>
		 * @param targetClass 目标类型
		 * @param otherCtorArgs 其他构造方法参数，为{@code null}或空忽略,否则要求数组元素不能为{@code null}
		 * @since 2.9.8
		 */
		public <C> C build(Class<C> targetClass,Object...otherCtorArgs) {
			try {
				checkArgument(targetClass != null,"targetClass is null");
				if(null == otherCtorArgs || 0 == otherCtorArgs.length) {
					return invokeConstructor(targetClass,this);
				}else {
					for(int i=0;i<otherCtorArgs.length;i++){
						checkNotNull(otherCtorArgs[i],"otherCtorArgs[%s] is null ",i);
					}
					Object[] args = new Object[otherCtorArgs.length+1];
					args[0] = this;
					System.arraycopy(otherCtorArgs, 0, args, 1, otherCtorArgs.length);
					return invokeConstructor(targetClass,args);
				}
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			} 
		}
	}
	public static class MethodInvokeFunction<T> implements Function<Object[], T>{
		private final Method method;
		private final Object target;
		public MethodInvokeFunction(Object target, Method method) {
			this.method = checkNotNull(method,"method is null") ;
			if(Modifier.isStatic(method.getModifiers())) {
				this.target = target;
			}else {
				this.target = checkNotNull(target,"target is null");
			}
		}
		public MethodInvokeFunction(Method method) {
			this(null,method);
		}
		public MethodInvokeFunction(Object target, Class<?>clazz,String name,Class<?>...parameterTypes) {
			this(target,methodOf(clazz,name,null, parameterTypes));
		}
		public MethodInvokeFunction(Class<?>clazz,String name,Class<?>...parameterTypes) {
			this(null,methodOf(clazz,name,true, parameterTypes));
		}
		public MethodInvokeFunction(Object target, String name,Class<?>...parameterTypes) {
			this(target,methodOf(checkNotNull(target,"target is null").getClass(),name,false, parameterTypes));
		}
		@SuppressWarnings("unchecked")
		@Override
		public T apply(Object[] input) {
			try {
				if(!method.isAccessible()) {
					method.setAccessible(true);
				}
				return (T) method.invoke(target, input);
				
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		private static Method methodOf(Class<?>clazz, String name,Boolean staticRequired, Class<?>...parameterTypes) {
			try {
				checkArgument(null!=parameterTypes && parameterTypes.length>0,"INVALID parameterTypes.length,>0 required");
				Method method = checkNotNull(clazz,"clazz is null").getMethod(checkNotNull(name,"name is null"), parameterTypes );
				if(Boolean.TRUE == staticRequired) {
					checkArgument(Modifier.isStatic(method.getModifiers()),"%s is not static method",method.toString());					
				}else if(Boolean.FALSE == staticRequired) {
					checkArgument(!Modifier.isStatic(method.getModifiers()),"%s is static method",method.toString());					
				}
				return method;
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
	}
}
